using System;
using System.Linq;
using HQLAddin.ConfigurationInspector;
using HQLAddin.Intellisense;
using HQLAddin.Util;
using HqlIntellisense.ConfigurationAbstraction;
using Microsoft.VisualStudio.Shell;
using Moq;
using NUnit.Framework;
using SharpTestsEx;
using System.Collections.Generic;

namespace HQLAddin.Tests.Intellisense
{
	[TestFixture]
	public class ConfigurationProviderTest
	{
		[Test]
		public void WhenImporstAreNullThenReturnEmptyDictionary()
		{
			var confProvider = new ConfigurationProvider(
				new Mock<IBuildWatcher>().Object, 
				new Mock<IPathProvider>().Object,
				new Mock<IAppDomainProxy>().Object, new TaskListStub(), 
				new Mock<IOutputPaneLogger>().Object);
			confProvider.Imports.Should().Be.Empty();
		}

		[Test]
		public void WhenPathsIsEmptyThenDontCallAppDomainCaller()
		{
			var pathProvider = new Mock<IPathProvider>();
			var caller = new Mock<IAppDomainProxy>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(Enumerable.Empty<string>());

			new ConfigurationProvider(
				new Mock<IBuildWatcher>().Object,
				pathProvider.Object,
				caller.Object, new TaskListStub(),
				new Mock<IOutputPaneLogger>().Object);

			caller.Verify(c => c.GetArtifacts(It.IsAny<string>()), Times.Never());
		}

		[Test]
		public void WhenPathsIsNotEmptyThenCallAppDomainCallerAndExposeResults()
		{
			var pathProvider = new Mock<IPathProvider>();
			
			var caller = new Mock<IAppDomainProxy>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(new[]{@"a:\b\c"});
			
			var imports = new Dictionary<string, string>();

			caller.Setup(c => c.GetArtifacts(@"a:\b\c")).Returns(new ConfigurationArtifacts
			                                                     	{
			                                                     		ClassInformation = Enumerable.Empty<IPersistentClassInformation>(),
			                                                     		Imports = imports
			                                                     	});

			var provider = new ConfigurationProvider(
				new Mock<IBuildWatcher>().Object,
				pathProvider.Object,
				caller.Object, new TaskListStub(),
				new Mock<IOutputPaneLogger>().Object);

			provider.FieldValue<IEnumerable<IPersistentClassInformation>>("classes")
				.Should().Be.Empty();

			provider.FieldValue<IDictionary<string, string>>("imports")
				.Should().Be.Empty();
		}

		[Test]
		public void WhenPathsHasTwoPathsThenExploreAndUnion()
		{
			var pathProvider = new Mock<IPathProvider>();

			var caller = new Mock<IAppDomainProxy>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(new[] { @"a:\b\c", @"d:\e\f" });

			var firstRead = new List<IPersistentClassInformation>{CreateClassInfo("Customer"), CreateClassInfo("Product")};
			var secondRead = new List<IPersistentClassInformation> { CreateClassInfo("Foo"), CreateClassInfo("Bar") };

			caller.Setup(c => c.GetArtifacts(@"a:\b\c")).Returns(new ConfigurationArtifacts
			{
				ClassInformation = firstRead
			});

			caller.Setup(c => c.GetArtifacts(@"d:\e\f")).Returns(new ConfigurationArtifacts
			{
				ClassInformation = secondRead
			});

			var provider = new ConfigurationProvider(
				new Mock<IBuildWatcher>().Object,
				pathProvider.Object,
				caller.Object, new TaskListStub(),
				new Mock<IOutputPaneLogger>().Object);

			provider.FieldValue<IEnumerable<IPersistentClassInformation>>("classes").Select(c => c.ClassName)
				.Should().Have.SameValuesAs("Customer", "Product", "Foo", "Bar");
		}
		
		[Test]
		public void WhenPathsHasTwoPathsThenExploreAndUnionImports()
		{
			var pathProvider = new Mock<IPathProvider>();

			var caller = new Mock<IAppDomainProxy>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(new[] { @"a:\b\c", @"d:\e\f" });

			caller.Setup(c => c.GetArtifacts(@"a:\b\c")).Returns(new ConfigurationArtifacts
			{
				ClassInformation = Enumerable.Empty<IPersistentClassInformation>(),
				Imports = new Dictionary<string, string>{{"Foo", "Bar"}, {"Bar", "Baz"}}

			});

			caller.Setup(c => c.GetArtifacts(@"d:\e\f")).Returns(new ConfigurationArtifacts
			{
				ClassInformation = Enumerable.Empty<IPersistentClassInformation>(),
				Imports = new Dictionary<string, string>{{"Foo1", "Bar"}, {"Bar1", "Baz"}}
			});

			var provider = new ConfigurationProvider(
				new Mock<IBuildWatcher>().Object,
				pathProvider.Object,
				caller.Object, new TaskListStub(),
				new Mock<IOutputPaneLogger>().Object);

			provider.FieldValue<IDictionary<string, string>>("imports").Select(kp => kp.Key)
				.Should().Have.SameValuesAs("Foo", "Bar", "Foo1", "Bar1");
		}

		[Test]
		public void WhenTwoPathsHasExportWithSameClassNameThenIncludeOnlyOnce()
		{
			var pathProvider = new Mock<IPathProvider>();

			var caller = new Mock<IAppDomainProxy>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(new[] { @"a:\b\c", @"d:\e\f" });

			var firstRead = new List<IPersistentClassInformation> { CreateClassInfo("Customer"), CreateClassInfo("Product") };
			var secondRead = new List<IPersistentClassInformation> { CreateClassInfo("Customer"), CreateClassInfo("Product") };

			caller.Setup(c => c.GetArtifacts(@"a:\b\c")).Returns(new ConfigurationArtifacts
			{
				ClassInformation = firstRead
			});

			caller.Setup(c => c.GetArtifacts(@"d:\e\f")).Returns(new ConfigurationArtifacts
			{
				ClassInformation = secondRead
			});

			var provider = new ConfigurationProvider(
				new Mock<IBuildWatcher>().Object,
				pathProvider.Object,
				caller.Object, new TaskListStub(),
				new Mock<IOutputPaneLogger>().Object);

			provider.FieldValue<IEnumerable<IPersistentClassInformation>>("classes").Select(c => c.ClassName)
				.Should().Have.SameValuesAs("Customer", "Product").And.Have.Count.EqualTo(2);
		}

		[Test]
		public void WhenPathsHasSameImportTwiceThenUseOnlyOnce()
		{
			var pathProvider = new Mock<IPathProvider>();

			var caller = new Mock<IAppDomainProxy>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(new[] { @"a:\b\c", @"d:\e\f" });

			caller.Setup(c => c.GetArtifacts(@"a:\b\c")).Returns(new ConfigurationArtifacts
			{
				ClassInformation = Enumerable.Empty<IPersistentClassInformation>(),
				Imports = new Dictionary<string, string> { { "Foo", "Bar" }, { "Bar", "Baz" } }

			});

			caller.Setup(c => c.GetArtifacts(@"d:\e\f")).Returns(new ConfigurationArtifacts
			{
				ClassInformation = Enumerable.Empty<IPersistentClassInformation>(),
				Imports = new Dictionary<string, string> { { "Foo", "Bar" }, { "Bar", "Baz" } }
			});

			var provider = new ConfigurationProvider(
				new Mock<IBuildWatcher>().Object,
				pathProvider.Object,
				caller.Object, new TaskListStub(),
				new Mock<IOutputPaneLogger>().Object);

			provider.FieldValue<IDictionary<string, string>>("imports").Select(kp => kp.Key)
				.Should().Have.SameValuesAs("Foo", "Bar").And.Have.Count.EqualTo(2);
		}

		[Test]
		public void WhenArtifactsHasErroresThenAddErrorsToTaskList()
		{
			var pathProvider = new Mock<IPathProvider>();

			var caller = new Mock<IAppDomainProxy>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(new[] { @"a:\b\c" });

			var errors = new List<ConfigurationError>
			             	{
			             		new ConfigurationError{Description = "Error1"},
								new ConfigurationError{Description = "Error2"}
			             	};

			caller.Setup(c => c.GetArtifacts(@"a:\b\c")).Returns(new ConfigurationArtifacts
			{
				Errors = errors
			});

			var errorList = new TaskListStub();


			new ConfigurationProvider(
				new Mock<IBuildWatcher>().Object,
				pathProvider.Object,
				caller.Object, errorList,
				new Mock<IOutputPaneLogger>().Object);

			errorList.Tasks.Select(t => t.Text).Should().Have.SameValuesAs(errors.Select(e => e.Description));
		}

		[Test]
		public void ClearTheErrorListOnEachBuild()
		{
			var pathProvider = new Mock<IPathProvider>();

			var caller = new Mock<IAppDomainProxy>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(new[] { @"a:\b\c" });

			var errors = new List<ConfigurationError>
			             	{
			             		new ConfigurationError{Description = "Error1"},
								new ConfigurationError{Description = "Error2"}
			             	};

			caller.Setup(c => c.GetArtifacts(@"a:\b\c")).Returns(new ConfigurationArtifacts
			{
				Errors = errors
			});

			var errorList = new TaskListStub();


			var buildWatcher = new Mock<IBuildWatcher>();
			new ConfigurationProvider(
				buildWatcher.Object,
				pathProvider.Object,
				caller.Object, errorList,
				new Mock<IOutputPaneLogger>().Object);

			buildWatcher.Raise(bw => bw.BuildSucced += null, EventArgs.Empty);


			errorList.Tasks.Count.Should().Be.EqualTo(2);
		}

		[Test]
		public void WhenAppDomainCallerFailDoNotFailTheConstruction()
		{
			var domainProxy = new Mock<IAppDomainProxy>();
			
			domainProxy.Setup(dp => dp.GetArtifacts(It.IsAny<string>()))
						.Throws(new InvalidOperationException());

			var pathProvider = new Mock<IPathProvider>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(new[] {"a"});

			Executing.This( () => 
					new ConfigurationProvider(new Mock<IBuildWatcher>().Object, 
											pathProvider.Object,
											domainProxy.Object,
											new Mock<ITaskList>().Object,
											new Mock<IOutputPaneLogger>().Object)).Should().NotThrow();
		}


		[Test]
		public void WhenAppDomainCallerFailThenExposeTheError()
		{
			var domainProxy = new Mock<IAppDomainProxy>();

			var exception = new InvalidOperationException();
			domainProxy.Setup(dp => dp.GetArtifacts(It.IsAny<string>()))
						.Throws(exception);

			var pathProvider = new Mock<IPathProvider>();
			pathProvider.Setup(pp => pp.FindPaths()).Returns(new[] { "a" });

			var logger = new Mock<IOutputPaneLogger>();


			new ConfigurationProvider(new Mock<IBuildWatcher>().Object,
											pathProvider.Object,
											domainProxy.Object,
											new Mock<ITaskList>().Object,
											logger.Object);

			logger.Verify(l => l.Log(It.IsAny<string>()));
		}

		public IPersistentClassInformation CreateClassInfo(string className)
		{
			return new PersistentClassInformation {ClassName = className};
		}
	}

	public class TaskListStub : ITaskList
	{
		public TaskListStub() 
		{
			Tasks = new List<Task>();
		}

		public ICollection<Task> Tasks { get; set; }

		public void Clear()
		{
			Tasks.Clear();
		}

		public void AddTask(Task task)
		{
			Tasks.Add(task);
		}

		public void Dispose()
		{}
	}
}